unit uOPAmpCalcForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, uBaseCalcForm, StdCtrls, ExtCtrls, ComCtrls;

type
  TfmOPAmpCalc = class(TfmBaseCalcForm)
    laDesign: TLabel;
    cbDesign: TComboBox;
    edRin: TLabeledEdit;
    edRFeed: TLabeledEdit;
    edAmp: TLabeledEdit;
    rbRin: TRadioButton;
    rbRFeed: TRadioButton;
    rbAmp: TRadioButton;
    laRIn: TLabel;
    laRFeed: TLabel;
    btnMatch: TButton;
    cbFindMatch: TCheckBox;
    Panel1: TPanel;
    Shape1: TShape;
    imgMain: TImage;
    cbESelect: TComboBox;
    lvMatches: TListView;
    Panel2: TPanel;
    procedure edRinChange(Sender: TObject);
    procedure edRinKeyPress(Sender: TObject; var Key: Char);
    procedure edAmpKeyPress(Sender: TObject; var Key: Char);
    procedure rbRinClick(Sender: TObject);
    procedure btnMatchClick(Sender: TObject);
    procedure cbFindMatchClick(Sender: TObject);
    procedure lvMatchesCompare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure cbDesignChange(Sender: TObject);
  private
    { Private declarations }
  protected
    function DoCheckFields: boolean; override;
    procedure FieldsChanged; override;
  public
    { Public declarations }
    class function CalcFriendlyName: string; override;
    class function RunKey: string; override;
    class procedure InvokeCalc; override;
    
    procedure AfterConstruction; override;
  end;

var
  fmOPAmpCalc: TfmOPAmpCalc;

implementation

{$R *.dfm}

uses uFormReg, uLocalization, uENoms;

{ TfmOPAmpCalc }

procedure TfmOPAmpCalc.AfterConstruction;
begin
  inherited;
  LocalizeStrings(cbDesign.Items);
  laDesign.Caption := GetLocaleString('dsgncaption');
  SetLabels('Amp', GetLocaleString('gain'), GetString('#global\ohm'));
  cbFindmatch.Caption := getLocaleString('findmatch');
  btnMatch.Caption := getLocaleString('dofindmatch');
  LocalizeListColumns(lvMatches);
  cbFindMatchClick(nil);
  cbESelect.ItemIndex := 1;
  cbDesign.ItemIndex := 0;
  cbDesignChange(nil);
  rbAmp.Checked := true;
  rbRinClick(nil);
end;

class function TfmOPAmpCalc.CalcFriendlyName: string;
begin
  Result := 'OP Amp calculator';
end;

function TfmOPAmpCalc.DoCheckFields: boolean;
begin
  Result := inherited DoCheckFields;
  
  if (rbAmp.Checked) then
    Result := CheckErrorField(edRIn, StrToValueEx(edRIn.Text,0)>100) and CheckErrorField(edRFeed, StrToValueEx(edRFeed.Text,0)>100);
  if (rbRIn.Checked) then
    Result := CheckErrorField(edRFeed, StrToValueEx(edRFeed.Text,0)>100) and (checkFieldMore(edAmp,0));
  if (rbRFeed.Checked) then
    Result := CheckErrorField(edRIn, StrToValueEx(edRIn.Text,0)>100) and (checkFieldMore(edAmp,0));
end;

procedure TfmOPAmpCalc.FieldsChanged;
var res: single;
begin
  inherited;

  if (rbAmp.Checked) then begin
    case cbDesign.ItemIndex of
      0: res := StrToValueEx(edRFeed.Text,0)/StrToValueEx(edRIn.Text,0);
      1: res := 1+(StrToValueEx(edRFeed.Text,0)/StrToValueEx(edRIn.Text,0));
    end;
    edAmp.Text := Format('%.2f', [res]);
  end;

  if (rbRFeed.Checked) then begin
    case cbDesign.ItemIndex of
      0: res := abs(checkField(edAmp)*StrToValueEx(edRIn.Text,1));
      1: res := abs((checkField(edAmp)-1)*StrToValueEx(edRIn.Text,1));
    end;
    edRFeed.Text := FormatValue(res);
  end;

  if (rbRIn.Checked) then begin
    case cbDesign.ItemIndex of
      0: res := abs(StrToValueEx(edRFeed.Text,1)/checkField(edAmp));
      1: res := abs(StrToValueEx(edRIn.Text,1)/(checkField(edAmp)-1));
    end;
    edRIn.Text := FormatValue(res);
  end;
end;

class procedure TfmOPAmpCalc.InvokeCalc;
begin
  Application.CreateForm(TfmOPAmpCalc, fmOPAmpCalc);
  fmOPAmpCalc.Show;
  inherited;
end;

class function TfmOPAmpCalc.RunKey: string;
begin
  Result := 'opamp';
end;

procedure TfmOPAmpCalc.edRinChange(Sender: TObject);
begin
  inherited;
  if DoCheckFields then Self.FieldsChanged;
end;

procedure TfmOPAmpCalc.edRinKeyPress(Sender: TObject; var Key: Char);
begin
  if not CheckKeyPressFloatMult(Key, false) then Key := #0;
end;

procedure TfmOPAmpCalc.edAmpKeyPress(Sender: TObject; var Key: Char);
begin
  if not CheckKeyPressFloat(Key, false) then Key := #0;
end;

procedure TfmOPAmpCalc.rbRinClick(Sender: TObject);
begin
  edAmp.ReadOnly := rbAmp.Checked;
  edRIn.ReadOnly := rbRIn.Checked;
  edRFeed.ReadOnly := rbRFeed.Checked;
  edRinChange(nil);
end;

procedure TfmOPAmpCalc.btnMatchClick(Sender: TObject);
const multi: array[0..2] of single = (1.0e3,1.0e4,1.0e5);
var r1,r2,amp,actamp,err,maxerr: single;
    r1i,mi: integer;
    range: array of single;
    series: TESeries;
begin
  lvMatches.Clear;
  
  amp := checkField(edAmp);
  if (amp <= 0) then exit;

  case cbESelect.ItemIndex of
    0: begin
         SetLength(range, Length(sfE12));
         move(sfE12[0], range[0], sizeOf(sfE12));
         series := esE12;
         maxerr := 10;
       end;
    1: begin
         SetLength(range, Length(sfE24));
         move(sfE24[0], range[0], sizeOf(sfE24));
         series := esE24;
         maxerr := 5;
       end;
    2: begin
         SetLength(range, Length(sfE48));
         move(sfE48[0], range[0], sizeOf(sfE48));
         series := esE48;
         maxerr := 2;
       end;
    3: begin
         SetLength(range, Length(sfE96));
         move(sfE96[0], range[0], sizeOf(sfE96));
         series := esE96;
         maxerr := 1;
       end;
    4: begin
         SetLength(range, Length(sfE192));
         move(sfE192[0], range[0], sizeOf(sfE192));
         series := esE192;
         maxerr := 0.5;
       end;
    else exit;
  end;

  for mi := Low(multi) to High(multi) do
    for r1i := Low(range) to High(range) do begin
      r1 := range[r1i]*multi[mi];
        case cbDesign.ItemIndex of
          0: begin
               r2 := amp*r1;
               r2 := GetNearest(r2, series, smrRound);
               actamp := r2/r1;
             end;
          1: begin
               r2 := (amp-1)*r1;
               r2 := GetNearest(r2, series, smrRound);
               actamp := 1+(r2/r1);
             end;  
        end;
        err := 100-(amp/actamp*100);
        if (abs(err)<maxerr) then
          with lvMatches.Items.Add do begin
            Caption := FormatValue(r1);
            SubItems.Add(FormatValue(r2));
            SubItems.Add(Format('%.2f', [actamp]));
            SubItems.Add(Format('%.2f', [err]));
          end;
    end;

  lvMatches.AlphaSort;
end;

procedure TfmOPAmpCalc.cbFindMatchClick(Sender: TObject);
begin
  btnMatch.Enabled := cbFindMatch.Checked;
  cbESelect.Enabled := cbFindMatch.Checked;
  edRIn.Enabled := not cbFindMatch.Checked;
  edRFeed.Enabled := not cbFindMatch.Checked;
  rbRin.Enabled := not cbFindMatch.Checked;
  rbAmp.Enabled := not cbFindMatch.Checked;
  rbRfeed.Enabled := not cbFindMatch.Checked;
  edAmp.ReadOnly := not cbFindMatch.Checked;
  if (cbFindMatch.Checked) then begin
    HighliteErrorField(edRin, false);
    HighliteErrorField(edRfeed, false);
    edAmp.SetFocus;
  end;
end;

procedure TfmOPAmpCalc.lvMatchesCompare(Sender: TObject; Item1,
  Item2: TListItem; Data: Integer; var Compare: Integer);
var a,b: single;
begin
  //Compare := CompareText(Item1.SubItems[Item1.SubItems.Count-1], Item2.SubItems[Item1.SubItems.Count-1]);
  a := abs(StrToFloat(Item1.SubItems[Item1.SubItems.Count-1]));
  b := abs(StrToFloat(Item2.SubItems[Item2.SubItems.Count-1]));
  if (a>b) then Compare := 1
  else
  if (b>a) then Compare := -1
  else Compare := 0;
end;

procedure TfmOPAmpCalc.cbDesignChange(Sender: TObject);
begin
  case cbDesign.ItemIndex of
    0: begin
         LoadPictureFromRes(imgMain.Picture, 'OPAMP_INVERT');
         SetLabels('RIn', 'Rin', GetString('#global\ohm'));
         SetLabels('RFeed', 'Rf', GetString('#global\ohm'));
         lvMatches.Columns[0].Caption := 'Rin';
         lvMatches.Columns[1].Caption := 'Rf';
       end;
    1: begin
         LoadPictureFromRes(imgMain.Picture, 'OPAMP_NONINVERT');
         SetLabels('RIn', 'R1', GetString('#global\ohm'));
         SetLabels('RFeed', 'R2', GetString('#global\ohm'));
         lvMatches.Columns[0].Caption := 'R1';
         lvMatches.Columns[1].Caption := 'R2';
       end;
  end;
  edRInChange(nil);
end;

initialization
  RegisterCalc(TfmOPAmpCalc);

end.
